package sh.orm;

import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import sh.orm.annotation.Id;
import sh.orm.cache.CacheManager;
import sh.orm.query.Page;
import sh.orm.query.Pageable;
import sh.orm.query.QueryCondition;
import sh.orm.util.NameUtil;
import sh.base.aop.util.ReflectUtil;
import sh.orm.util.ConnectionPool;

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 每个session持有一个数据库连接<br>
 * 执行完一个操作之后，数据库连接自动释放。
 * <b>用户使用完之后，必须通过调用session#close()关闭session,否则持有的数据库连接不能释放<b/>
 * TODO 通过拦截器的方式，执行sql操作前获取连接，执行完之后释放连接。
 *
 * <br>
 * 支持session级别的缓存
 *
 * @author lqq
 * @date 2019/1/28
 */
@Slf4j
public class Session {

    /** 可以使用单例 */
    private ConvertContext convertContext = new ConvertContext();

    /** 执行查询的目标class类型 */
    private Class<?> targetClass ;

    private Connection connection;

    private SessionFactory sessionFactory;

    /**
     * todo 缓存的策略
     *  session级别的缓存对象，执行过增删改操作之后，缓存会被清除
     *  使用ConcurrentHashMap，处理并发
     *  默认大小是1024个对象，
     * key ： class + id ；
     * value ：缓存的对象
     * */
    Map<String,Object> cacheMap = null;

    /** 一级缓存的大小，默认是1024个对象 TODO 为啥要这么个数字？ */
    private int cacheSize = 1024;

    /** 是否开启一级缓存，默认开启 */
    private Boolean cacheOpen = true;

    /** 二级缓存 TODO 需要工厂初始化 */
    @Setter
    private CacheManager cacheManager;

    /** 是否开启二级缓存，默认关闭 */
    private Boolean secondCacheOpen = false;

    /**
     * 初始化
     * @param sessionFactory
     */
    Session(SessionFactory sessionFactory){
        this(sessionFactory, null);
    }

    public Session(SessionFactory sessionFactory, CacheManager cacheManager) {
        this.sessionFactory = sessionFactory;
        connection = ConnectionPool.getInstance().getConnection();
        if(cacheOpen){
            cacheMap = new ConcurrentHashMap<>(cacheSize);
        }
        if(cacheManager != null){
            this.cacheManager = cacheManager;
            this.secondCacheOpen = true;
        }
    }


    public <T> void save(T entity) {
        Connection conn = this.getConnection();
        try {
            Statement statement = conn.createStatement();

            String sql = SqlFactory.getInstance().getInsertSql(entity);

            statement.execute(sql);

        } catch (SQLException e) {
            e.printStackTrace();
            throw new OrmException(e.getMessage());
        }
    }

    /**
     * 删除多个
     * @param ids
     * @param clazz
     * @return
     */
    public void delete(Long[] ids,Class<?> clazz) {
        for(Long id : ids){
            delete(id, clazz);
        }
    }


    public boolean delete(Long id,Class<?> clazz) {
        String sql  = SqlFactory.getInstance().getDeleteSql(clazz , id);

        Statement statement ;
        Connection conn = this.getConnection();
        try {
            statement = conn.createStatement();
        } catch (SQLException e) {
            e.printStackTrace();
            throw new OrmException("创建Statement异常" + e.getMessage());
        }

        boolean ifSuccess = false;
        try {
            ifSuccess = statement.execute(sql);
        } catch (SQLException e) {
            e.printStackTrace();
            throw new OrmException("执行更新异常" + e.getMessage());
        }

        clearCache(id, clazz);

        return ifSuccess;
    }

    /**
     * 执行更新操作
     * @param entity
     * @param <T>
     * @return
     */
    public <T> int update(T entity) {

        String sql  = SqlFactory.getInstance().getUpdateSql(entity);

        Statement statement ;
        Connection conn = this.getConnection();
        try {
            statement = conn.createStatement();
        } catch (SQLException e) {
            e.printStackTrace();
            throw new OrmException("创建Statement异常" + e.getMessage());
        }

        int rowCount = 0;
        try {
            rowCount = statement.executeUpdate(sql);
        } catch (SQLException e) {
            e.printStackTrace();
            throw new OrmException("执行更新异常" + e.getMessage());
        }

        clearCache(entity);

        return rowCount;
    }



    public <T> T getById(Long id, Class<T> clazz) {

        //先检查是否开启缓存，如果开启缓存，试着从缓存中获取对象
        T obj = null;
        if(cacheOpen){
            obj = (T)cacheMap.get(NameUtil.getNoProxyName(clazz.getName()) + id);
            if(obj != null){
                log.info("从一级缓存中拿到了数据： " + id);
                return obj;
            }
        }

        if(secondCacheOpen){
            obj = cacheManager.get(NameUtil.getNoProxyName(clazz.getName()) + id, clazz);
            if(obj != null){
                log.info("从二级缓存中拿到了数据： " + id);
                return obj;
            }
        }

        //ResultSet,
        //通过反射设置值，
        //返回对象

        String sql = SqlFactory.getInstance().getQuerySql(clazz,id);
        Statement statement ;
        Connection conn = this.getConnection();
        try {
            statement = conn.createStatement();
        } catch (SQLException e) {
            e.printStackTrace();
            throw new OrmException("创建Statement异常" + e.getMessage());
        }

        ResultSet rs = null;
        try {
            rs = statement.executeQuery(sql);
        } catch (SQLException e) {
            e.printStackTrace();
            throw new OrmException("执行查询异常" + e.getMessage());
        }

        obj = convertContext.convertToEntity(rs,clazz);
        putCache(obj);
        return obj;
    }


    /**
     * 执行查询
     * @param sql
     * @param clazz 返回值的泛型类型
     * @return
     */
    public List query(String sql , Class<?> clazz) {
        ResultSet rs = null;
        try {
            Statement statement = connection.createStatement();
            rs = statement.executeQuery(sql);

        } catch (SQLException e) {
            e.printStackTrace();
            throw new OrmException(e.getMessage());
        }

        List dataList = convertContext.convertToEntityList(rs,clazz);
        putCache(dataList, clazz);
        return dataList;
    }


    /**
     * TODO 关联关系的缓存先不管
     * 执行关联关系的查询
     * 执行要查询的方法，为了反射所使用
     * 要执行查询，本质上只需要三个条件即可，
     * 1.关联关系的注解
     * 2.连接的注解
     * 3.当前对象的id，即根据这个id进行查询。
     *
     * @param method get方法
     * @param proxyObject 原始对象的代理对象，因为代理对象的id是已经赋值过的
     * @return
     */
    public Object query(Method method , Object proxyObject) {

        Connection conn = this.getConnection();
        ResultSet rs = null;
        try {
            String sql = SqlFactory.getInstance().getQuerySql(method , proxyObject);
            Statement statement = conn.createStatement();
            rs = statement.executeQuery(sql);

        } catch (SQLException e) {
            e.printStackTrace();
            throw new OrmException(e.getMessage());
        }

        Type returnType = method.getReturnType();
        if(returnType == List.class){
            Class<?> genericClass = ReflectUtil.getListGenericClass(method.getGenericReturnType());
            return convertContext.convertToEntityList(rs,genericClass);
        }
        // 简单的对象类型
        if(StaticEntityHolder.getStaticEntity((Class<?>) returnType) != null){
            return convertContext.convertToEntity(rs,(Class<?>) returnType);
        }

        //剩下的暂未实现
        throw new OrmException("未定义的解析类型" + returnType);

    }



    /**
     * 查询返回分页内容，包括总数和list数据
     * @param queryCondition
     * @param pageable
     * @param clazz
     * @param <T>
     * @return
     */
    public <T> Page<T> selectPageByQueryCondition(QueryCondition queryCondition, Pageable pageable, Class<T> clazz) {

        //查询总数,查询完之后不能返回连接，因为要接着用。
        int total = selectCountByQueryCondition(clazz, queryCondition);

        //查询list
        List<T> dataList = selectListByQueryCondition(queryCondition, pageable, clazz);
        Page<T> page = new Page<>();
        page.setDataList(dataList);
        page.setTotal(total);

        return page;
    }


    /**
     * 查询指定分页的内容
     * <b>带分页功能</b>
     * @param queryCondition
     * @param pageable
     * @param clazz
     * @param <T>
     * @return
     */
    public <T> List<T> selectListByQueryCondition(QueryCondition queryCondition, Pageable pageable, Class<T> clazz){
        ResultSet rs = null;
        try {
            Statement statement = connection.createStatement();

            String sql = SqlFactory.getInstance().getSelectLimitSql(clazz, queryCondition, pageable);

            rs = statement.executeQuery(sql);

        } catch (SQLException e) {
            e.printStackTrace();
            throw new OrmException(e.getMessage());
        }

        List<T> dataList = convertContext.convertToEntityList(rs,clazz);

        putCache(dataList, clazz);

        return dataList;

    }

    /**
     * 根据查询条件查询总数
     * @param queryCondition
     * @return
     */
    public int selectCountByQueryCondition( Class<?> clazz ,QueryCondition queryCondition){

        String sql  = SqlFactory.getInstance().getCountSql(clazz, queryCondition);

        Statement statement ;
        Connection conn = this.getConnection();
        try {
            statement = conn.createStatement();
        } catch (SQLException e) {
            e.printStackTrace();
            throw new OrmException("创建Statement异常" + e.getMessage());
        }

        ResultSet rs;
        try {
            rs = statement.executeQuery(sql);
        } catch (SQLException e) {
            e.printStackTrace();
            throw new OrmException("执行更新异常" + e.getMessage());
        }
        return convertContext.convertToInt(rs);
    }

    //TODO 优化，查询list和查询entity只是参数和返回值不同，可以合并


    public <T> List<T> selectByQueryCondition(QueryCondition queryCondition, Class<T> clazz) {

        Connection conn = this.getConnection();
        ResultSet rs = null;
        try {
            Statement statement = conn.createStatement();

            String sql = SqlFactory.getInstance().getSelectSql(clazz,queryCondition);

            rs = statement.executeQuery(sql);

        } catch (SQLException e) {
            e.printStackTrace();
            throw new OrmException(e.getMessage());
        }

        List<T> dataList = convertContext.convertToEntityList(rs,clazz);

        putCache(dataList, clazz);

        return dataList;
    }

    /**
     * 这是一个测试接口，获取所有数据，正式使用不用这个
     * @param clazz
     * @param <T>
     * @return
     */
    public <T> List<T> getAll(Class<T> clazz) {
        return selectByQueryCondition(new QueryCondition(), clazz);
    }



    public Connection getConnection(){
        return connection;
    }

    /**
     * 开启事务
     */
    public void beginTransaction(){
        try {
            connection.setAutoCommit(false);
        } catch (SQLException e) {
            e.printStackTrace();
            throw new OrmException("开启事务异常" + e.getMessage());
        }
    }

    /**
     * 提交事务
     */
    public void commit(){
        try {
            connection.commit();
        } catch (SQLException e) {
            throw new OrmException("提交事务异常" + e.getMessage());
        }
    }

    /**
     * 回滚事务
     */
    public void rollback(){
        try {
            connection.rollback();
        } catch (SQLException e) {
            throw new OrmException("回滚事务异常" + e.getMessage());
        }
    }

    /**
     * 断开数据库连接
     */
    public void disconnect() throws SQLException {
        connection.close();
    }

    /**
     * 关闭session并释放连接
     */
    public void close(){
        try {
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
            throw new OrmException("关闭数据库连接异常" + e.getMessage());
        }
        sessionFactory.removeSession();
    }


    /**
     * 清除单个缓存
     * 如果缓存开启，就清除，否则不做操作
     * @param id
     * @param clazz
     */
    private void clearCache(Long id , Class<?> clazz ){
        if(cacheOpen){
            cacheMap.remove(clazz.getName() + id);
        }
    }

    /**
     * 清除缓存
     * @param entity
     */
    private void clearCache(Object entity){
        if(cacheOpen){
            Long id = ReflectUtil.getFieldValueWithAnnotation(entity, entity.getClass(), Id.class);
            clearCache(id, entity.getClass());
        }
    }

    /**
     * 缓存一个链表，被缓存的数据一定是有@Id的，否则不进行缓存，
     * @param dataList
     * @param clazz
     */
    private void putCache(List<?> dataList , Class<?> clazz ){
        if(cacheOpen){
            Long id;
            for(Object obj : dataList){
                //如果没有id，直接跳出
                if((id = ReflectUtil.getFieldValueWithAnnotation(obj, obj.getClass(), Id.class)) == null){
                    break;
                }
                putCache(obj, id);
            }
        }
    }

    /**
     * 放入缓存
     * @param entity
     * @param id
     */
    private void putCache(Object entity, Long id){
        if(cacheOpen){
            cacheMap.put(NameUtil.getNoProxyName(entity.getClass().getName()) + id, entity);
        }
        if(secondCacheOpen){
            cacheManager.put(NameUtil.getNoProxyName(entity.getClass().getName()) + id, entity);
        }
    }

    /**
     * 放入缓存
     * @param entity 必须含有id
     */
    private void putCache(Object entity){
        if(cacheOpen){
            putCache(entity, ReflectUtil.getFieldValueWithAnnotation(entity, entity.getClass(), Id.class));
        }
    }


    /**
     * 清空所有缓存
     */
    public void flush(){
        if(cacheOpen){
            cacheMap.clear();
        }
    }

}
